inter-process communication
Table of Contents
IPC Shared Memory
Accessing a Shared Memory Segment
int shmget(key_t key, size_t size, int shmflg);
is used to obtain access to a shared memory segment.
- The
key
argument is a access value associated with the semaphore ID. - The
size
argument is the size in bytes of the requested shared memory. - The
shmflg
argument specifies the initial access permissions and creation control flags. - When the call succeeds, it returns the shared memory segment ID. This call is also used to get the ID of an existing shared segment (from a process requesting sharing of some existing memory portion).
#include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> ... key_t key; /* key to be passed to shmget() */ int shmflg; /* shmflg to be passed to shmget() */ int shmid; /* return value from shmget() */ int size; /* size to be passed to shmget() */ ... key = ... size = ... shmflg) = ... if ((shmid = shmget (key, size, shmflg)) == -1) { perror("shmget: shmget failed"); exit(1); } else { (void) fprintf(stderr, "shmget: shmget returned %d\n", shmid); exit(0); }
- Controlling a Shared Memory Segment
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
is used to alter the permissions and other characteristics of a shared memory segment.
The cmd argument is one of following control commands:
SHM_LOCK
– Lock the specified shared memory segment in memory. The process must have the effective ID of superuser to perform this command.SHM_UNLOCK
– Unlock the shared memory segment. The process must have the effective ID of superuser to perform this command.IPC_STAT
– Return the status information contained in the control structure and place it in the buffer pointed to by buf. The process must have read permission on the segment to perform this command.IPC_SET
– Set the effective user and group identification and access permissions. The process must have an effective ID of owner, creator or superuser to perform this command.IPC_RMID
– Remove the shared memory segment.
The
buf
is a sructure of typestruct shmid_ds
which is defined in<sys/shm.h>
.#include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> ... int cmd; /* command code for shmctl() */ int shmid; /* segment ID */ struct shmid_ds shmid_ds; /* shared memory data structure to hold results */ ... shmid = ... cmd = ... if ((rtrn = shmctl(shmid, cmd, shmid_ds)) == -1) { perror("shmctl: shmctl failed"); exit(1); }
Attaching and Detaching a Shared Memory Segment
shmat()
and shmdt()
are used to attach and detach shared memory
segments.
void *shmat(int shmid, const void *shmaddr, int shmflg); int shmdt(const void *shmaddr);
shmat()
returns a pointer, shmaddr, to the head of the shared segment associated with a valid shmid.shmdt()
detaches the shared memory segment located at the address indicated by shmaddr.
#include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> static struct state { /* Internal record of attached segments. */ int shmid; /* shmid of attached segment */ char *shmaddr; /* attach point */ int shmflg; /* flags used on attach */ } ap[MAXnap]; /* State of current attached segments. */ int nap; /* Number of currently attached segments. */ ... char *addr; /* address work variable */ register int i; /* work area */ register struct state *p; /* ptr to current state entry */ ... p = &ap[nap++]; p->shmid = ... p->shmaddr = ... p->shmflg = ... p->shmaddr = shmat(p->shmid, p->shmaddr, p->shmflg); if(p->shmaddr == (char *)-1) { perror("shmop: shmat failed"); nap--; } else (void) fprintf(stderr, "shmop: shmat returned %#8.8x\n", p->shmaddr); ... i = shmdt(addr); if(i == -1) { perror("shmop: shmdt failed"); } else { (void) fprintf(stderr, "shmop: shmdt returned %d\n", i); for (p = ap, i = nap; i--; p++) if (p->shmaddr == addr) *p = ap[--nap]; }
Example
shm_server.c
– simply creates the string and shared memory portion.shm_client.c
– attaches itself to the created shared memory portion and uses the string
# After running the Server you can see the attached Shared Memory $ipcs -m ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status 0x0000162e 4292614 xxx 666 27 1 # After running the client the memory is freed. $ipcs -m ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status 0x0000162e 4292614 xxx 666 27 0
shm_server.c
#include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <stdio.h> #define SHMSZ 27 main() { char c; int shmid; key_t key; char *shm, *s; /* * We'll name our shared memory segment * "5678". */ key = 5678; /* * Create the segment. */ if ((shmid = shmget(key, SHMSZ, IPC_CREAT | 0666)) < 0) { perror("shmget"); exit(1); } /* * Now we attach the segment to our data space. */ if ((shm = shmat(shmid, NULL, 0)) == (char *) -1) { perror("shmat"); exit(1); } /* * Now put some things into the memory for the * other process to read. */ s = shm; for (c = 'a'; c <= 'z'; c++) *s++ = c; *s = NULL; /* * Finally, we wait until the other process * changes the first character of our memory * to '*', indicating that it has read what * we put there. */ while (*shm != '*') sleep(1); exit(0); }
shm_client.c
#include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <stdio.h> #define SHMSZ 27 main() { int shmid; key_t key; char *shm, *s; /* * We need to get the segment named * "5678", created by the server. */ key = 5678; /* * Locate the segment. */ if ((shmid = shmget(key, SHMSZ, 0666)) < 0) { perror("shmget"); exit(1); } /* * Now we attach the segment to our data space. */ if ((shm = shmat(shmid, NULL, 0)) == (char *) -1) { perror("shmat"); exit(1); } /* * Now read what the server put in the memory. */ for (s = shm; *s != NULL; s++) putchar(*s); putchar('\n'); /* * Finally, change the first character of the * segment to '*', indicating we have read * the segment. */ *shm = '*'; exit(0); }
POSIX Shared Memory
POSIX shared memory is actually a variation of mapped memory. The
major differences are to use shm_open()
to open the shared memory
object (instead of calling open()
) and use shm_unlink()
to close and
delete the object (instead of calling close()
which does not remove
the object).
- Create the memory segment
Create shared memory segment by using
shm_open()
. Theshm_open()
call establishes a connection between a shared memory object and a file descriptor. It creates an open file description that refers to the shared memory object and a file descriptor that refers to that open file description.#include <sys/mman.h> int shm_open(const char *name, int oflag, mode_t mode);
- The name argument points to a string naming a shared memory object.
- If successful,
shm_open()
returns a file descriptor for the shared memory object that is the lowest numbered file descriptor not currently open for that process. - oflag: file access modes
O_RDONLY
will create a read-only segment.O_RDWR
will create a segment that we can read and write from/to the memory segment.O_CREAT
creates the segment if it does not exist or a handle to it if it does exist.O_EXCL
will return an error if the segment already exists.
- The last parameters are the file access permissions.
- Set the size of the memory segment
#include <unistd.h> int ftruncate(int file_descriptor, off_t length);
- Map the shared memory region
#include <sys/mman.h> void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
The
mmap()
function establishes a mapping between the address space of the process for len bytes to the memory object represented by the file descriptor fd at offset off for len bytes.The
mmap()
returns a pointer to the shared memory segment. After the process finished, it can unmap it from the address space of the process usingmunmap()
call:#include <sys/mman.h> int munmap(void *addr, size_t len)
The shared memory can be removed from the system using
shm_unlink()
. It causes the shared memory to be deleted when the last process detaches from it.#include <sys/mman.h> int shm_unlink(const char *name)
- Example
#include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <sys/wait.h> #include <sys/mman.h> #include <pthread.h> int main() { pthread_mutexattr_t attributes; pthread_mutexattr_init(&attributes); pthread_mutexattr_setpshared(&attributes, PTHREAD_PROCESS_SHARED); int handle = shm_open("/shm", O_CREAT | O_RDWR, 0777); ftruncate(handle, 2048*sizeof(int)); char *memory = (char *)mmap(0, 2048*sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, handle, 0); // mutex share pthread_mutex_t *mutex = (pthread_mutex_t*)memory; pthread_mutex_init(mutex, &attributes); pthread_mutexattr_destroy(&attributes); // variable share int *count = (int*)(memory + sizeof(pthread_mutex_t)); *count = 0; printf("Initial count = %d\n", *count); int value_returned_from_child = 0; pid_t pid = fork(); if (pid == 0) { pthread_mutex_lock(mutex); (*count)++; printf("Child process increased the count to %d\n", *count); pthread_mutex_unlock(mutex); value_returned_from_child = 99; } else { int status; // waiting for the child process to finish waitpid(pid, &status, 0); value_returned_from_child = WEXITSTATUS(status); printf("value_returned_from_child = %d\n", value_returned_from_child); pthread_mutex_lock(mutex); (*count)++; printf("Parent process increased the count to %d\n", *count); pthread_mutex_unlock(mutex); } munmap(memory, 2048*sizeof(int)); shm_unlink("/shm"); return value_returned_from_child; }
g++ -o ipc ipc.cc -Wall -lpthread -lrt
The parent process sets up a mutex that's shared with its child process. Then, the parent forks a child, and wait for the child to complete (
waitpid()
).The macro
WEXITSTATUS
does the conversion of the exit status fromwaitpid()
into the return value from the child process.
Sharing semaphores between processes
#include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <semaphore.h> int main() { int status; sem_t *semaphore = sem_open("/sema", O_CREAT, 0777, 1); pid_t pid = fork(); if (pid == 0) { printf("child\n"); sem_post(semaphore); sem_close(semaphore); } else { sem_wait(semaphore); printf("parent\n"); sem_close(semaphore); sem_unlink("/sema"); } return 0; }
Message queues
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <string.h> #include <sys/wait.h> #include <mqueue.h> #define PMODE 0655 int main() { int status; struct mq_attr attr; attr.mq_maxmsg = 10; attr.mq_msgsize = 20; pid_t pid = fork(); // child process - sending message if (pid == 0) { char message[20]; strncpy(message, "Hello Parent!", 13); // write/create - attr needed because of O_CREAT mqd_t mqfd = mq_open("/test1", O_WRONLY|O_CREAT, PMODE, &attr); if(mqfd == -1) { perror("Child mq_open failure"); exit(0); } status = mq_send(mqfd, message, strlen(message)+1, 0); if (status == -1) { perror("mq_send failure\n"); } else { printf("Child is sending message: %s\n", message); printf("mq_send successful\n"); } mq_close(mqfd); printf("Child process done\n"); } else { // parent - receiving message // read only mqd_t mqfd = mq_open("/test1", O_RDONLY|O_CREAT, PMODE, &attr); if (mqfd == -1) { perror("Parent mq_open failure"); exit(0); } // Parent is waiting for the child process to finish waitpid(pid, &status, 0); char buf[100]; status = mq_receive(mqfd, buf, 100, 0); if (status == -1) { perror("mq_receive failure\n"); } else { printf("mq_receive successful\n"); printf("Parent received message: %s\n", buf); } mq_close(mqfd); mq_unlink("/test1"); printf("Parent process done\n"); } return 0; }
Socket
server.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <pthread.h> void error(const char *msg) { perror(msg); exit(1); } void* echo(void* param) { char buf[1024]; int count; pthread_detach(pthread_self()); int s = (int)param; while (count = read(s, buf, 1023) > 0) { printf("Server received %s\n", buf); printf("Server sending it back\n"); write(s, buf, strlen(buf)); } close(s); } int main(int argc, char *argv[]) { int sockfd, newsockfd, portno = 9999; // create a TCP/IP socket sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) error("ERROR opening socket"); struct sockaddr_in serv_addr; // clear address structure bzero((char *) &serv_addr, sizeof(serv_addr)); /* setup the host_addr structure for use in bind call */ // server byte order serv_addr.sin_family = AF_INET; // automatically be filled with current host's IP address serv_addr.sin_addr.s_addr = INADDR_ANY; // port number to bind to serv_addr.sin_port = htons(portno); // This bind() call will bind the socket to the current IP address on port if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) { error("ERROR on binding"); } // This listen() call tells the socket to listen to the incoming connections. // The listen() function places all incoming connection into a backlog queue // until accept() call accepts the connection. // Here, we set the maximum size for the backlog queue to 5. listen(sockfd, 5); while (newsockfd = accept(sockfd, 0, 0)) { pthread_t t; pthread_create(&t, 0, echo, (void*)newsockfd); } return 0; }
client.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> void error(const char *msg) { perror(msg); exit(0); } int main(int argc, char *argv[]) { int sockfd, portno = 9999; struct sockaddr_in serv_addr; sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) error("ERROR opening socket"); bzero(&serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); serv_addr.sin_port = htons(portno); if (connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) == 0) { printf("Client sending 'hello server!' to server\n"); char buf[1024]; strncpy(buf, "hello server!", 20); write(sockfd, buf, strlen(buf)); int count = read(sockfd, buf, 1024); printf("Got echo of %s from server\n", buf); shutdown(sockfd, SHUT_RDWR); } else error("ERROR connecting"); return 0; }
Pipes
Anonymous Pipes
#include <stdio.h> #include <unistd.h> int main() { int status, myPipe[2]; pipe(myPipe); // create the pipe pid_t pid = fork(); /* child process */ if (pid == 0) { close(myPipe[0]); // close unused read end write(myPipe[1], "a", 1); printf("Child process sent 'a'\n"); close(myPipe[1]); } else { /* parent process */ char buffer[21]; close(myPipe[1]); // close unused write end int pid_child = wait(&status); int length = read(myPipe[0], buffer, 20); buffer[length] = '\0'; printf("Parent process received '%s'\n", buffer); close(myPipe[0]); } return 0; }
Named Pipes
#include <stdio.h> #include <unistd.h> #include <sys/stat.h> #include <fcntl.h> int main() { int status; mknod("/tmp/pipefile", S_IFIFO | S_IRUSR | S_IWUSR, 0); pid_t pid = fork(); /* child process */ if (pid == 0) { int myPipe = open("/tmp/pipefile", O_WRONLY); write(myPipe, "a", 1); printf("Child process sent 'a'\n"); close(myPipe); } else { /* parent process */ int myPipe = open("/tmp/pipefile", O_RDONLY); char buffer[21]; int pid_child = wait(&status); int length = read(myPipe, buffer, 20); buffer[length] = '\0'; printf("Parent process received '%s'\n", buffer); close(myPipe); } unlink("/tmp/pipefile"); return 0; }